//
// ServiceStack.OrmLite: Light-weight POCO ORM for .NET and Mono
//
// Authors:
//   Demis Bellot (demis.bellot@gmail.com)
//
// Copyright 2013 ServiceStack, Inc. All Rights Reserved.
//
// Licensed under the same terms of ServiceStack.
//

using System;
using System.Collections.Generic;
using System.Data;
using ServiceStack.Logging;

namespace ServiceStack.OrmLite;

public static class OrmLiteConfig
{
    public const string IdField = "Id";

    private const int DefaultCommandTimeout = 30;//in seconds
    private static int? commandTimeout;

    /// <summary>
    /// Gets or sets the wait time before terminating the attempt to execute a command and generating an error(in seconds).
    /// </summary>
    public static int CommandTimeout
    {
        get => commandTimeout ?? DefaultCommandTimeout;
        set => commandTimeout = value;
    }

    private static IOrmLiteDialectProvider dialectProvider;
    public static IOrmLiteDialectProvider DialectProvider
    {
        get
        {
            if (dialectProvider == null)
            {
                throw new ArgumentNullException(nameof(DialectProvider),
                    "You must set the singleton 'OrmLiteConfig.DialectProvider' to use the OrmLiteWriteExtensions");
            }
            return dialectProvider;
        }
        set => dialectProvider = value;
    }

    public static IOrmLiteDialectProvider GetDialectProvider(this IDbCommand dbCmd) =>
        dbCmd is IHasDialectProvider hasDialectProvider 
            ? hasDialectProvider.DialectProvider
            : DialectProvider;

    public static IOrmLiteDialectProvider Dialect(this IDbCommand dbCmd) =>
        dbCmd is IHasDialectProvider hasDialectProvider 
            ? hasDialectProvider.DialectProvider
            : DialectProvider;

    public static IOrmLiteDialectProvider GetDialectProvider(this IDbConnection db) =>
        db is IHasDialectProvider hasDialectProvider
            ? hasDialectProvider.DialectProvider
            : DialectProvider;

    public static INamingStrategy GetNamingStrategy(this IDbConnection db) =>
        db.GetDialectProvider().NamingStrategy;

    public static IOrmLiteDialectProvider Dialect(this IDbConnection db) =>
        db is IHasDialectProvider hasDialectProvider
            ? hasDialectProvider.DialectProvider
            : DialectProvider;

    public static IOrmLiteExecFilter GetExecFilter(this IOrmLiteDialectProvider dialectProvider) {
        return dialectProvider != null
            ? dialectProvider.ExecFilter ?? ExecFilter
            : ExecFilter;
    }

    public static IOrmLiteExecFilter GetExecFilter(this IDbCommand dbCmd) {
        var dialect = dbCmd is IHasDialectProvider hasDialectProvider
            ? hasDialectProvider.DialectProvider
            : DialectProvider;
        return dialect.GetExecFilter();
    }

    public static IOrmLiteExecFilter GetExecFilter(this IDbConnection db) {
        var dialect = db is IHasDialectProvider hasDialectProvider
            ? hasDialectProvider.DialectProvider
            : DialectProvider;
        return dialect.GetExecFilter();
    }

    public static void SetLastCommandText(this IDbConnection db, string sql)
    {
        if (db is OrmLiteConnection ormLiteConn)
        {
            ormLiteConn.LastCommandText = sql;
        }
    }

    public static void SetLastCommand(this IDbConnection db, IDbCommand dbCmd)
    {
        if (db is OrmLiteConnection ormLiteConn)
        {
            ormLiteConn.LastCommand = dbCmd;
        }
    }

    public static IDbConnection WithTag(this IDbConnection db, string name)
    {
        if (db is OrmLiteConnection ormLiteConn)
        {
            ormLiteConn.Tag = name;
        }
        return db;
    }
    
    public static string GetTag(this IDbConnection db) => db is OrmLiteConnection ormLiteConn ? ormLiteConn.Tag : null;
    
    public static string GetTag(this IDbCommand db) => db is OrmLiteCommand ormLiteCmd 
        ? ormLiteCmd.OrmLiteConnection.GetTag() 
        : null;

#if NET8_0_OR_GREATER
    public static TimeSpan? GetElapsedTime(this IDbCommand db) => db is OrmLiteCommand ormLiteCmd 
        ? ormLiteCmd.GetElapsedTime() 
        : null;
#endif

    private const string RequiresOrmLiteConnection = "{0} can only be set on a OrmLiteConnectionFactory connection, not a plain IDbConnection";

    /// <summary>
    /// Sets the wait time before terminating the attempt to execute a command and generating an error.
    /// </summary>
    /// <param name="db"></param>
    /// <param name="commandTimeout">Command execution timeout(in seconds)</param>
    /// <exception cref="NotImplementedException"></exception>
    public static void SetCommandTimeout(this IDbConnection db, int? commandTimeout)
    {
        if (db is not OrmLiteConnection ormLiteConn)
            throw new NotImplementedException(string.Format(RequiresOrmLiteConnection,nameof(CommandTimeout)));

        ormLiteConn.CommandTimeout = commandTimeout;
    }

    /// <summary>
    /// <inheritdoc cref="SetCommandTimeout(IDbConnection,int?)"/>
    /// </summary>
    /// <param name="db"></param>
    /// <param name="commandTimeout">Command execution timeout</param>
    public static void SetCommandTimeout(this IDbConnection db, TimeSpan? commandTimeout)
    {
        SetCommandTimeout(db, (int?)commandTimeout?.TotalSeconds);
    }

    public static IDbConnection ToDbConnection(this string dbConnectionStringOrFilePath)
    {
        return dbConnectionStringOrFilePath.ToDbConnection(DialectProvider);
    }

    public static IDbConnection OpenDbConnection(this string dbConnectionStringOrFilePath)
    {
        var sqlConn = dbConnectionStringOrFilePath.ToDbConnection(DialectProvider);
        sqlConn.Open();
        return sqlConn;
    }

    public static IDbConnection OpenReadOnlyDbConnection(this string dbConnectionStringOrFilePath)
    {
        var options = new Dictionary<string, string> { { "Read Only", "True" } };

        var dbConn = DialectProvider.CreateConnection(dbConnectionStringOrFilePath, options);
        dbConn.Open();
        return dbConn;
    }

    public static void ClearCache()
    {
        OrmLiteConfigExtensions.ClearCache();
    }

    public static ModelDefinition GetModelMetadata(this Type modelType)
    {
        return modelType.GetModelDefinition();
    }

    public static IDbConnection ToDbConnection(this string dbConnectionStringOrFilePath, IOrmLiteDialectProvider dialectProvider)
    {
        var dbConn = dialectProvider.CreateConnection(dbConnectionStringOrFilePath, options: null);
        return dbConn;
    }

    public static void ResetLogFactory(ILogFactory logFactory=null)
    {
        logFactory ??= LogManager.LogFactory;
        LogManager.LogFactory = logFactory;
        OrmLiteLog.Log = logFactory.GetLogger(typeof(OrmLiteLog));
    }
        
    public static bool DisableColumnGuessFallback { get; set; }
    public static bool StripUpperInLike { get; set; } 
#if NETCORE
        = true;
#endif

    public static IOrmLiteResultsFilter ResultsFilter
    {
        get
        {
            var state = OrmLiteContext.OrmLiteState;
            return state?.ResultsFilter;
        }
        set => OrmLiteContext.GetOrCreateState().ResultsFilter = value;
    }

    private static IOrmLiteExecFilter execFilter;
    public static IOrmLiteExecFilter ExecFilter
    {
        get 
        {
            if (execFilter == null)
                execFilter = new OrmLiteExecFilter();

            return dialectProvider != null 
                ? dialectProvider.ExecFilter ?? execFilter 
                : execFilter; 
        }
        set => execFilter = value;
    }

    public static Action<IDbCommand> BeforeExecFilter { get; set; }
    public static Action<IDbCommand> AfterExecFilter { get; set; }

    public static Action<IDbCommand, object> InsertFilter { get; set; }
    public static Action<IDbCommand, object> UpdateFilter { get; set; }
    public static Action<IUntypedSqlExpression> SqlExpressionSelectFilter { get; set; }
    public static Func<Type, string, string> LoadReferenceSelectFilter { get; set; }


    public static Func<string, string> StringFilter { get; set; }

    public static Func<FieldDefinition, object> OnDbNullFilter { get; set; }

    public static Action<object> PopulatedObjectFilter { get; set; }

    public static Action<IDbCommand, Exception> ExceptionFilter { get; set; }

    public static bool ThrowOnError { get; set; } = true;

    public static Func<string, string> SanitizeFieldNameForParamNameFn = fieldName =>
        (fieldName ?? "").Replace(" ", "");

    public static bool IsCaseInsensitive { get; set; }

    public static bool DeoptimizeReader { get; set; }

    public static bool SkipForeignKeys { get; set; }

    public static bool IncludeTablePrefixes { get; set; }
        
    public static Action<IUntypedSqlExpression> SqlExpressionInitFilter { get; set; }

    public static Func<string, string> ParamNameFilter { get; set; }
        
    public static Action<ModelDefinition> OnModelDefinitionInit { get; set; }
}